﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Management.Deployment;


namespace ServerApp {

// the file association is defined in the Registry
// 
// here is a possible .reg file that setup our association
//  Windows Registry Editor Version 6.00
//
//  [HKEY_CLASSES_ROOT\.StoreAppWorkload-Acme]
//  @="ServerApp.StoreAppWorkload-Acme"
//
//  [HKEY_CLASSES_ROOT\ServerApp.StoreAppWorkload-Acme]
//  @="StoreApp Request Workload"
//
//  [HKEY_CLASSES_ROOT\ServerApp.StoreAppWorkload-Acme\DefaultIcon]
//  @="%SystemRoot%\\System32\\imageres.dll,-169"
//
//  [HKEY_CLASSES_ROOT\ServerApp.StoreAppWorkload-Acme\shell]
//
//  [HKEY_CLASSES_ROOT\ServerApp.StoreAppWorkload-Acme\shell\open]
//
//  [HKEY_CLASSES_ROOT\ServerApp.StoreAppWorkload-Acme\shell\open\command]
//  @="\"C:\\ACME\\ServerApp.exe\" \"%1\""
//


    public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private const String APP_ID = "CompanyStore.ClientToast";
    private bool installing = false;
    private bool debug = false;

    const string WSA_FILE_EXTENSION = "wsaparam";
    const string WSA_FILE_EXTENSION_WITH_DOT = ".wsaparam";

    const string REG_DESKTOP_FILE_EXTENSION = "StoreAppWorkload-Acme";
    const string REG_DESKTOP_FILE_EXTENSION_WITH_DOT = "." + REG_DESKTOP_FILE_EXTENSION;
    const string REG_DESKTOP_DOCUMENT_NAME = "ServerApp.StoreAppWorkload-Acme";
    const string REG_DESKTOP_DOCUMENT_DEFAULT_TEXT = "StoreApp Request Workload";

    const string REG_DESKTOP_PROTOCOL = "daipp";
    const string REG_DESKTOP_PROTOCOL_URL = "daipp://";
    const string REG_DESKTOP_PROTOCOL_FRIENDLY_NAME = "Desktop Application Inter-Process Protocol";
    

    // 
    // The expected url is daipp://<command>?<param>=<value>
    // current supported commands:
    //  launch  -- launch the given WSA
    //      identityName= value of the Name attribute of the Identity element in the AppManifest file
    //          ex: <Identity Name="61a5760c-ef57-41f1-99fa-b9b223d071e0"...
    //      appId= value of the Id attribute of the Application element under <Applications> in the AppManifest file
    //          ex: <Application Id="App" 
    // 
    //  install
    //      
    //      
    //  launchOrInstall -- laucnh the given WSA if installed, otherwise, install it and start it
    //      identityName= value of the Name attribute of the Identity element in the AppManifest file
    //          ex: <Identity Name="61a5760c-ef57-41f1-99fa-b9b223d071e0"...
    //      appId= value of the Id attribute of the Application element under <Applications> in the AppManifest file
    //          ex: <Application Id="App" 
    //      appxFilename= .appx file to install if not present (might be a file from a share)
    //  
    private void MainForm_Load(object sender, EventArgs e)
    {
        // enlarge the width to let the user see the full pathname 
        Width = 1024;
        
        Assembly program = Assembly.GetEntryAssembly();
        string fullProcessFilename = program.Location;

        // check the command line
        string workload;



        //testCertInstall(@"C:\Users\ajourn\Documents\Visual Studio 2012\Projects\Enterprise Store\Enterprise Store\bin\Release\toBuild\MycertTest.cer");

        // "process path name.exe" workloadFile.registeredExtension
        string cmdLineTemp = Environment.CommandLine;
        //cmdLineTemp += @"daipp://WINDOWSLEGACYAPP?path=C:%5CProgram%20Files%5CInternet%20Explorer%5Ciexplore.exe";
        //cmdLineTemp += @"daipp://internalWin8App?tata=9ead678e-9870-4da3-8ed1-bc28f3911e03&app=App&toto=%5C%5CLUL-USP-01%5Cfrbddcst%5CMBIC%20Apps%5CMcs.SlateFx8_0.7.0.3_x64_Test%5CMcs.SlateFx8_0.7.0.3_x64.appx";
        //cmdLineTemp += @"daipp://internalWin8App?tata=CatalogueMBIC&app=App&toto=%5C%5CLUL-USP-01%5Cfrbddcst%5CMBIC%20Apps%5CCatalogue%20mbic%5CCatalogueMBIC_1.0.0.1_AnyCPU.appx";
        //cmdLineTemp += @"daipp://internalWin8App?identity=MDTMetro?app=App?Path=\\AJOURN-X1\CompanyStoreClient\sample app\MDT Metro monitoring_1.0.0.9_AnyCPU.appx";
        string tempChar = "";
        string cmdLine = "";

        for (int cptChar = 0; cptChar < cmdLineTemp.Length; cptChar++)
        {
            if (cmdLineTemp[cptChar] == '%' && cptChar + 2 < cmdLineTemp.Length)
            {
                char a = cmdLineTemp[cptChar + 1];
                char b = cmdLineTemp[cptChar + 2];
                string c = String.Concat(a,b);
                tempChar = Char.ConvertFromUtf32(Convert.ToInt32(c,16));
                cptChar += 2;
                //remove duplicate slash
                cmdLine += tempChar.Replace(@"\\",@"\");
            }
            else
            {
                cmdLine += cmdLineTemp[cptChar];
            }
        }

        if (cmdLine.ToUpper().Contains("?DEBUG=TRUE"))
        {
            cmdLine = cmdLine.ToUpper().Replace("?DEBUG=TRUE","");
            debug = true;
            this.tbCommandLine.Visible = true;
        }
        
        cmdLine += @"daipp://WINDOWSSTOREWIN8APP?identity=CaptainDash.CAPTAINDASH?appID=App?storeID=CaptainDash.CAPTAINDASH_7wcdh3qgb8zjc";
        //cmdLine += @"daipp://internalWin8App?tata=ea594181-fec2-4267-a2af-ae7130edad4a?app=App?toto=C:\Users\ajourn\Documents\Visual Studio 2012\Projects\MDT Metro monitoring\MDT Metro monitoring\AppPackages\MDT Metro monitoring_1.0.0.8_AnyCPU_Test\MDT Metro monitoring_1.0.0.8_AnyCPU.appx";
        //cmdLine += @"daipp://WINDOWSLEGACYAPP?exePath=C:\Program Files\Internet Explorer\iexplore.exe http://skydrive.com";
        int pos = cmdLine.LastIndexOf(@"ServerApp.exe") + 15; // Note: count the SPACE after the .exe full path
       // if(pos > 0)
            workload = cmdLine.Substring(pos);
//       
        //tbCommandLine.Text = cmdLine + "#" + workload;

        if (!string.IsNullOrEmpty(workload))
        {
            tbCommandLine.Text = workload;
            ExecuteWorkload(workload);
        }
        else
        {
            //Theses lines are for test only:
                //this one only works if you have the application available on your harddrive, use another appx file if needed
                //ExecuteWorkload(@"daipp://internalWin8App?tata=ea594181-fec2-4267-a2af-ae7130edad4a?app=App?toto=C:\Users\ajourn\Documents\Visual Studio 2012\Projects\MDT Metro monitoring\MDT Metro monitoring\AppPackages\MDT Metro monitoring_1.0.0.8_AnyCPU_Test\MDT Metro monitoring_1.0.0.8_AnyCPU.appx");
                //ExecuteWorkload(@"daipp://windowsStoreWin8app?tata=ShazamEntertainmentLtd.Shazam?toto=App");
                //ExecuteWorkload(@"daipp://windowsLegacyApp?toto=C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.EXE");
            tbCommandLine.Text = "<no workload>";
        }
    }       

    private Package GetInstallationId(string identityName)
    {
        PackageManager manager = new PackageManager();
        IEnumerable<Package> installedPackages = manager.FindPackagesForUser(/*null after CP*/ WindowsIdentity.GetCurrent().User.Value);
        
        foreach (var package in installedPackages)
        {
            
            if (identityName.CompareTo(package.Id.Name) == 0)
            {
                return package;
            }
        }
        
        return null;
    }

    private bool UninstallPackage(Package app)
    {

        return true;
    }

    private void ExecuteProtocol(object queryString)
    {
        // extract parameters from the query string
        string[] queryStringparts = ((string)queryString).Split('?');

        Regex r = new Regex(@"", RegexOptions.IgnoreCase);

        if (queryStringparts.Length >= 2)
        {
            // Note: a trailing / is added to the protocol so we need to remove it
            if (queryStringparts[0].EndsWith("/"))
                queryStringparts[0] = queryStringparts[0].Substring(0, queryStringparts[0].Length - 1);

            // daipp://command
            string command = queryStringparts[0].Substring(queryStringparts[0].LastIndexOf('/') + 1);
            
            //Query is used for compatibility for users that are still using the ? char as delimiter for actions
            string[] query;

            if (queryStringparts.Length <= 2 && command.ToUpper() != "WINDOWSLEGACYAPP")
            {
                query = queryStringparts[1].Split('&');                
            }
            else
            {
                query = new string[queryStringparts.Length-1];
                for (int i = 1; i < queryStringparts.Length; i++)
                {
                    query[i-1] = queryStringparts[i];
                }
            }

            switch (command.ToUpper())
            {

                case "WINDOWSLEGACYAPP":
                {
                    //  launch  -- launch the given WSA
                    //      exeName= value of the EXE file to launch
                    //          ex: excel.exe

                    if (query.Length != 1)
                    {
                        this.display(3, "Protocol syntax error. It should be something like daipp://WindowsLegacyApp?exePath=Path To Exe");
                        return;
                    }

                    int equalpos = query[0].LastIndexOf("=") + 1;
                    int extentionpos = query[0].ToUpper().LastIndexOf(".EXE");
                    String commande = query[0];

                    String exePath = commande.Substring(equalpos, extentionpos-equalpos+4);
                    String param = query[0].Substring(extentionpos + 4);
                    
                    if (!File.Exists(exePath))
                    {
                        this.display(3, "Attention the file " + exePath + " does not exist");
                    }
                    else
                    {
                        Process exe = new Process();
                        exe.StartInfo.FileName = exePath;
                        if (param.Trim().Length >0 )
                            exe.StartInfo.Arguments = param;
                        exe.Start();
                        Application.Exit();
                    }

                }
                break;
                
                case "WINDOWSSTOREWIN8APP":
                {
                    //  launch  -- launch the given WSA
                    //      identityName= value of the Name attribute of the Identity element in the AppManifest file
                    //          ex: <Identity Name="61a5760c-ef57-41f1-99fa-b9b223d071e0"...
                    //      appId= value of the Id attribute of the Application element under <Applications> in the AppManifest file
                    //          ex: <Application Id="App" 
                    //      storeID= value of the id of the application in the store. composed of the identityName followed by _something
                    //          you can find it in %localappdata%\Packages it's the name of the application folder

                    if (query.Length != 3)
                    {
                        this.display(3, "Protocol syntax error. It should be something like daipp://windowsStoreWin8app?identityName=Identity?appID=ApplicationID?storeID=StoreID");
                        return;
                    }

                    try 
                    {
                        string identityName = query[0].Split('=')[1];
                        string storeID = query[2].Split('=')[1];
                        string appId = query[1].Split('=')[1];
                    
                        // check if the App is already installed and returns the corresponding installation ID
                        Package package = GetInstallationId(identityName);
                        if (package == null)
                        {
                            string uri = "ms-windows-store:PDP?PFN=" + storeID;
                            Process p = Process.Start(uri);
                            Application.Exit();
                        }
                        else
                        {
                            // TODO: find out how to build the parameter to the ShellObj.dll method
                            uint pid = 0;
                            //string id = "<identityName>_<installationId>!<appId>";
                            int pos = package.Id.FullName.LastIndexOf("_");
                            string installationID = package.Id.FullName.Substring(pos + 1);
                            string id = string.Format("{0}_{1}!{2}", identityName, installationID, appId);

                            ApplicationActivationManager appActiveManager = new ApplicationActivationManager();
                            appActiveManager.ActivateApplication(id, null, ActivateOptions.None, out pid);

                            if (pid == 0)
                            {
                                this.display(3, "unknown error");
                            }
                            else
                            {
                                Application.Exit();
                            }
                        }

                    }
                    catch (Exception)
                    {
                        this.display(3, "unknown error");
                        return;
                    }
                }
                    break;

                case "INTERNALWIN8APP":
                {
                    //  launch  -- launch the given WSA
                    //      identityName= value of the Name attribute of the Identity element in the AppManifest file
                    //          ex: <Identity Name="61a5760c-ef57-41f1-99fa-b9b223d071e0"...
                    //      appId= value of the Id attribute of the Application element under <Applications> in the AppManifest file
                    //          ex: <Application Id="App" 
                    //      packagePath= path to the appx file to install
                    //          ex: packagePath=\\server\share\app1\app1.appx

                    //if needed here is the regex : ^(([a-z0-9]+)(\?)([a-z]+)(=)([^\?]+)(\??)([a-z]+)(=)([^\?]+)(\??)([a-z]+)(=)([^\?]+))$

                    if (query.Length != 3)
                    {
                        this.display(3, "Protocol syntax error. It should be something like daipp://InternalWin8App?identityName=Identity?appID=ApplicationID?PackagePath=Path to the appx");
                        return;
                    }

                    string identityName = query[0].Split('=')[1];
                    string appId = query[1].Split('=')[1];
                    string inputPackageUri = query[2].Split('=')[1];
                    bool updating = false;
                    
                    // check if the App is already installed and returns the corresponding installation ID
                    Package package = GetInstallationId(identityName);
                    if (package != null)
                    {
                        //if application is already installed check if the application is up to date
                        // the version of the application package should be written in a version.txt file aside of the appx package
                        this.display(3, "Validating access to the application package...");

                        //locate the version.txt file
                        this.display(3, "Checking the version of the application package online");
                        string directory = inputPackageUri.Substring(0, inputPackageUri.LastIndexOf(@"\"));
                        var files = Directory.GetFiles(directory + @"\", "version.txt");
                        if (files != null && files.Length != 0)
                        {
                            StreamReader versionFile = new StreamReader(files[0]);
                            if (versionFile != null)
                            {
                                var upToDateVersion = versionFile.ReadLine().Split('.');

                                if (upToDateVersion.Length != 4)
                                {
                                    this.display(3, "Application version should be written like this 1.0.0.12 (major.minor.build.revision)");
                                }
                                else
                                {
                                    //Check is the package is uptodate
                                    if (Int32.Parse(upToDateVersion[0]) >= package.Id.Version.Major
                                        && Int32.Parse(upToDateVersion[1]) >= package.Id.Version.Minor
                                        && Int32.Parse(upToDateVersion[2]) >= package.Id.Version.Build
                                        && Int32.Parse(upToDateVersion[3]) > package.Id.Version.Revision)
                                    {
                                        //The package is not up to date, 
                                        //  uninstall previous application and install the new one
                                        //  but first save the local state and the settings of the application
                                        this.display(3, "Backuping application data and settings");
                                        string localstate = Environment.GetEnvironmentVariable("LOCALAPPDATA") + @"\Packages\" + package.Id.FamilyName + @"\LocalState";
                                        string dest = Environment.GetEnvironmentVariable("TEMP") + @"\CompanyStore";

                                        string localsettings = Environment.GetEnvironmentVariable("LOCALAPPDATA") + @"\Packages\" + package.Id.FamilyName + @"\Settings";

                                        if(!Directory.Exists(dest))
                                            Directory.CreateDirectory(dest);

                                        if (Directory.Exists(dest + @"\LocalState"))
                                            Directory.Delete(dest + @"\LocalState",true);

                                        if(Directory.Exists(dest + @"\Settings"))
                                            Directory.Delete(dest + @"\Settings",true);
                                        
                                        Directory.Move(localstate, dest + @"\LocalState");
                                        Directory.Move(localsettings, dest + @"\Settings");
                                        updating = true;
                                        //Uninstall
                                        PackageManager packageManager = new PackageManager();

                                        IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deploymentOperation =
                                            packageManager.RemovePackageAsync(package.Id.FullName);


                                        // This event is signaled when the operation completes
                                        ManualResetEvent opCompletedEvent = new ManualResetEvent(false);
                                        this.display(3, "Uninstalling " + identityName);
                                        // Define the delegate using a statement lambda
                                        deploymentOperation.Completed = (depProgress, actionInProgress) => { opCompletedEvent.Set(); };

                                        // Wait until the operation completes
                                        opCompletedEvent.WaitOne();

                                        this.display(0, null);
                                        // Check the status of the operation
                                        System.Threading.Thread.Sleep(100);
                                        if (deploymentOperation.Status == AsyncStatus.Error)
                                        {
                                            DeploymentResult deploymentResult = deploymentOperation.GetResults();
                                            this.display(3, "Error code: " + deploymentOperation.ErrorCode + "\n" + "Error text: " + deploymentResult.ErrorText);
                                        }
                                        else if (deploymentOperation.Status == AsyncStatus.Canceled)
                                        {
                                            this.display(3, "Uninstallation canceled");
                                        }
                                        else if (deploymentOperation.Status == AsyncStatus.Completed)
                                        {
                                            this.display(3, "Uninstallation succeeded");
                                            package = null;
                                        }
                                        else
                                        {
                                            this.display(3, "Uninstallation status unknown");
                                            package = null;
                                        }
                                    }
                                }
                                
                            }

                        }

                    }
                    if (package == null)
                    //else
                    {
                        //Install the application and its dependencies
                        var deploymentOperation = installApplicationPackage(identityName, inputPackageUri);

                        
                        // Check the status of the operation
                        System.Threading.Thread.Sleep(100);
                        if (deploymentOperation.Status == AsyncStatus.Error)
                        {
                            DeploymentResult deploymentResult = deploymentOperation.GetResults();
                            this.display(3, "Error code: "+deploymentOperation.ErrorCode + "\n" + "Error text: " + deploymentResult.ErrorText);
                        }
                        else if (deploymentOperation.Status == AsyncStatus.Canceled)
                        {
                            this.display(3, "Installation canceled");
                        }
                        else if (deploymentOperation.Status == AsyncStatus.Completed)
                        {
                            this.display(3, "Installation succeeded");
                            package = GetInstallationId(identityName);
                        }
                        else
                        {
                            this.display(3, "Installation status unknown");
                            package = GetInstallationId(identityName);
                        }

                        if (updating)
                        {
                            this.display(3, "Restoring application data and settings");
                            string localstate = Environment.GetEnvironmentVariable("LOCALAPPDATA") + @"\Packages\" + package.Id.FamilyName + @"\LocalState";
                            string dest = Environment.GetEnvironmentVariable("TEMP") + @"\CompanyStore";

                            string localsettings = Environment.GetEnvironmentVariable("LOCALAPPDATA") + @"\Packages\" + package.Id.FamilyName + @"\Settings";

                            Directory.Delete(localstate,true);
                            Directory.Move(dest + @"\LocalState", localstate);
                            Directory.Delete(localsettings,true);
                            Directory.Move(dest + @"\Settings", localsettings);
                                        
                        }

                    }
                    if (package != null)
                    {
                        RunApplication(identityName, appId, package);
                        Application.Exit();
                    }
                    else
                    {
                        this.display(3, "Unable to launch the application");
                    }

                }
                break;

                default:
                    this.display(3, "Unknown action.");
                break;
            }
            
        }
        else
        {
            this.display(3, "Protocol syntax error.");
        }
    }

    private IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> installApplicationPackage(string identityName, string inputPackageUri)
    {
        string directory = "";
        string[] files;
        try
        {
            var isTrusted = true;
            this.display(3, "Validating access to the application package...");

            //locate and install the certificate
            this.display(3, "Installing the certificate if needed");

            directory = inputPackageUri.Substring(0, inputPackageUri.LastIndexOf(@"\"));
            files = Directory.GetFiles(directory + @"\", "*.cer");
            if (files != null)
            {
                foreach (string file in files)
                {
                    testCertInstall(file);
                }
            }

            files = Directory.GetFiles(directory + @"\", "*.pfx");
            if (files != null)
            {
                foreach (string file in files)
                {
                    isTrusted = isTrusted && testCertInstall(file);
                }
            }
        }
        catch (Exception e)
        {
            this.display(3, "Error while checking the certificate. Skipping it");
        }

        //if the package is not installed, install it.
        Uri packageUri = new Uri(inputPackageUri);

        List<Uri> dependencies = null;
        //see if the package has dependencies
        if (Directory.Exists(directory + @"\Dependencies"))
        {
            this.display(3, "Installing dependencies...");
            dependencies = new List<Uri>();

            files = Directory.GetFiles(directory + @"\Dependencies", "*.appx");
            foreach (string file in files)
            {
                dependencies.Add(new Uri(file));
            }

            string proc_arch = string.Empty;

            switch (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"))
            {
                case "amd64": proc_arch = "x64"; break;
                case "x86": proc_arch = "x64"; break;
            }

            if (Directory.Exists(directory + @"\Dependencies\" + proc_arch + @"\"))
            {
                dependencies = new List<Uri>();

                files = Directory.GetFiles(directory + @"\Dependencies\" + proc_arch + @"\", "*.appx");
                foreach (string file in files)
                {
                    dependencies.Add(new Uri(file));
                }
            }

            
        }

        PackageManager packageManager = new PackageManager();

        IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deploymentOperation =
            packageManager.AddPackageAsync(
                packageUri,
                dependencies,
                DeploymentOptions.ForceApplicationShutdown);


        // This event is signaled when the operation completes
        ManualResetEvent opCompletedEvent = new ManualResetEvent(false);
        this.display(1, identityName);
        // Define the delegate using a statement lambda
        deploymentOperation.Completed = (depProgress, actionInProgress) => { opCompletedEvent.Set(); };



        // Wait until the operation completes
        opCompletedEvent.WaitOne();
        this.display(0, null);

        return deploymentOperation;
    }

    private bool IsAnAdministrator()
    {
        WindowsIdentity identity =  WindowsIdentity.GetCurrent();
        WindowsPrincipal principal =  new WindowsPrincipal(identity);
        return principal.IsInRole(WindowsBuiltInRole.Administrator);
    }

    private bool testCertInstall(String certificateFile)
    {

        X509Certificate2 cert = new X509Certificate2(certificateFile);
        if (!cert.Verify())
        {
            if (!IsAnAdministrator())
            {
                MessageBox.Show("You need administrative rights to install the certificate for this application." + Environment.NewLine + "If you don't have admin rigths please contact your administrator else please accept next prompt from application install_cert.exe");
                this.display(3, "certificate path is " + certificateFile);
                Process p = new Process();
                p.StartInfo.FileName = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf('\\')) + @"\install_cert.exe";
                p.StartInfo.Arguments = "\""+certificateFile+"\"";
                try
                {
                    p.Start();
                    
                }
                catch (Win32Exception e)
                {
                    MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            else
            {

                X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
                store.Open(OpenFlags.ReadWrite);
                store.Add(cert);
                store.Close();
            }

        }
        return cert.Verify();
    }

    /// <summary>
    /// This function is used to update the GUI using the dispatcher
    /// </summary>
    /// <param name="element">The element to update</param>
    /// <param name="data">The data to display</param>
    private void UpdateUI(int element, object data)
    {

        switch (element)
        {
            case 3: tbWorkload.AppendText(Environment.NewLine);
                    tbWorkload.AppendText(data.ToString()); break;
            case 2: progress(); break;
            case 0: installing = false; break;
            case 1:
                {
                    System.Threading.Thread prog = new System.Threading.Thread(startProgress);
                    prog.Start(data);
                }; break;
            default: break;
        }
    }

    /// <summary>
    /// The delegate to call to update the UI - reference the UpdateUI function
    /// </summary>
    /// <param name="element"></param>
    /// <param name="data"></param>
    /// <see cref="UpdateUI"/>
    private delegate void UpdateUIDelegate(int element, object data);

    /// <summary>
    /// Use this function to display something on the GUI from another thread
    /// </summary>
    /// <param name="element">The element in the GUI to update</param>
    /// <param name="data">The object to display</param>
    internal void display(int element, object data)
    {
        try
        {
            this.BeginInvoke(new MainForm.UpdateUIDelegate(UpdateUI), element, data);
        }
        catch (Exception e)
        {
            

        }
    }

    private void startProgress(object appName)
    {
        installing = true;
        this.display(3, (string)appName + "...");
        
        while (installing)
        {
            this.display(2, null);
            System.Threading.Thread.Sleep(100);
        }
        this.display(3, (string)appName + " installed.");
    }

    /// <summary>
    /// This function is used to change the value of the progress bar
    /// </summary>
    private void progress()
    {
        if (actionInProgress.Value + 8 > actionInProgress.Maximum)
        {
            actionInProgress.Value = 0;
        }
        else
        {
            actionInProgress.Value += 8;
        }
    }


    private void RunApplication(string identityName, string appId, Package package)
    {
        // TODO: find out how to build the parameter to the ShellObj.dll method
        uint pid = 0;
        //string id = "<identityName>_<installationId>!<appId>";
        int pos = package.Id.FullName.LastIndexOf("_");
        string installationID = package.Id.FullName.Substring(pos + 1);
        string id = string.Format("{0}_{1}!{2}", identityName, installationID, appId);

        ApplicationActivationManager appActiveManager = new ApplicationActivationManager();
        appActiveManager.ActivateApplication(id, null, ActivateOptions.None, out pid);

        if (pid == 0)
        {
            // TODO: error while launching the WSA
        }
    }

    private void ShowParametersFromFile(string filename)
    {
        try
        {
            bool exists = File.Exists(filename);
            using (StreamReader sr = new StreamReader(filename))
            {
                String line = sr.ReadToEnd();
                tbWorkload.Text = line;
            }
        }
        catch (IOException x)
        {
            Debug.WriteLine(x.ToString());
        }
    }

    private void ExecuteWorkload(string workload)
    {
        Debug.WriteLine(workload);

        if (workload.StartsWith(REG_DESKTOP_PROTOCOL))
        {
            System.Threading.Thread prog = new System.Threading.Thread(ExecuteProtocol);
            prog.Start(workload);
            
        }
        else  // the workload is the filename that contains the data sent by the WSA
        {
            ShowParametersFromFile(workload);
        }
    }

}


// From http://answers.flyppdevportal.com/categories/metro/csharpvb.aspx?ID=a4d2fca1-4034-4cc7-a86a-6242ce1a8b16
// 
public enum ActivateOptions
{
    None = 0x00000000,  // No flags set
    DesignMode = 0x00000001,  // The application is being activated for design mode, and thus will not be able to
    // to create an immersive window. Window creation must be done by design tools which
    // load the necessary components by communicating with a designer-specified service on
    // the site chain established on the activation manager.  The splash screen normally
    // shown when an application is activated will also not appear.  Most activations
    // will not use this flag.
    NoErrorUI = 0x00000002,  // Do not show an error dialog if the app fails to activate.                                
    NoSplashScreen = 0x00000004,  // Do not show the splash screen when activating the app.
}

[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IApplicationActivationManager
{
    // Activates the specified immersive application for the "Launch" contract, passing the provided arguments
    // string into the application.  Callers can obtain the process Id of the application instance fulfilling this contract.
    IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
    IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
    IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
}

[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]//Application Activation Manager
class ApplicationActivationManager : IApplicationActivationManager
{
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
    public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
}


}
